#include <stddef.h>
#include <syscall.h>
#include <types.h>
#include <stdio.h>
#include <malloc.h>
#include <ipc.h>
#include <gato/render.h>
#include <list.h>
#include <event.h>
#include <input-event-codes.h>
#include <wm-types.h>
#include "wm-private.h"

static void wm_merge_update_region(region_t r)
{
    wm_send_render_event((struct wm_render_event_t){
        .type = WM_UPDATE,
        .update_event = {
            .region = r,
        }});
}

static wid_t wm_window_create_with_connect(int x, int y, int width, int height, color_t bg, slot_t client, window_connect_t *connect)
{
    window_t *w = calloc(1, sizeof(window_t));

    w->config.x = x;
    w->config.y = y;
    w->config.width = width;
    w->config.height = height;
    w->bg = bg;
    w->vmo = usys_vmo_create(width * height * sizeof(color_t), VMO_DATA);
    w->event = usys_fifo_create(sizeof(struct input_event_t) * 64);
    init_list_head(&w->node);

    color_t *pixel = (color_t *)usys_vmo_map(w->vmo, NULL, PROT_READ | PROT_WRITE, 0);
    surface_wrap(w->surface, pixel, width, height);
    surface_clear(w->surface, bg, 0, 0, width, height);

    if (connect)
    {
        connect->event = usys_slot_copy(client, w->event);
        connect->vmo = usys_slot_copy(client, w->vmo);
    }
    return wid_alloc(w);
}

static void wm_window_destory(wid_t id)
{
    window_t *w = win_get(id);

    if (w)
    {
        surface_free(w->surface);
        free(w);
        wid_free(id);
    }
}

static bool_t wm_window_config_set(wid_t id, struct window_config_t *config)
{
    window_t *w = win_get(id);

    if (!w)
        return FALSE;

    w->config = *config;
    return TRUE;
}

static bool_t wm_window_config_get(wid_t id, struct window_config_t *config)
{
    window_t *w = win_get(id);

    if (!w)
        return FALSE;
    *config = w->config;
    return TRUE;
}

static void wm_window_event_mask_set(wid_t id, uint64_t event_mask)
{
    window_t *w = win_get(id);
    if (!w)
        return;
    w->config.event_mask = event_mask;
}

static bool_t wm_window_show(wid_t id)
{
    window_t *w = win_get(id);

    if (!w)
        return FALSE;

    wm_raise(id);

    wm_merge_update_region((region_t){w->config.x, w->config.y, w->surface->width, w->surface->height});
    return TRUE;
}

static bool_t wm_window_hide(wid_t id)
{
    window_t *w = win_get(id);

    if (!w)
        return FALSE;

    list_del_init(&w->node);
    return TRUE;
}

static void wm_window_update(wid_t id)
{
    window_t *w = win_get(id);
    region_t r1 = {w->config.x, w->config.y, w->surface->width, w->surface->height};
    wm_merge_update_region(r1);
}

static void wm_window_raise(wid_t id)
{
    window_t *w = win_get(id);
    wm_raise(id);
    wm_merge_update_region((region_t){w->config.x, w->config.y, w->surface->width, w->surface->height});
}

static void wm_window_move(wid_t id, int dx, int dy)
{
    window_t *w = win_get(id);
    w->config.x += dx;
    w->config.y += dy;
    wm_window_update(id);
}

u64_t wm_dispatch(ipc_msg_t *ipc_msg)
{
    struct wm_request *fr = ipc_get_msg_data(ipc_msg);

    switch (fr->req)
    {
    case WM_WINDOW_CREATE:
    {
        GET_WM_REQUEST(param, WM_WINDOW_CREATE, fr);
        return wm_window_create_with_connect(param->x, param->y, param->width, param->height, param->bg, ipc_msg->client_slot, &param->connect);
    }
    case WM_WINDOW_DESTORY:
    {
        GET_WM_REQUEST(param, WM_WINDOW_DESTORY, fr);
        wm_window_destory(param->id);
        break;
    }
    case WM_WINDOW_CONFIG_SET:
    {
        GET_WM_REQUEST(param, WM_WINDOW_CONFIG_SET, fr);
        return wm_window_config_set(param->id, &param->config);
    }
    case WM_WINDOW_CONFIG_GET:
    {
        GET_WM_REQUEST(param, WM_WINDOW_CONFIG_GET, fr);
        return wm_window_config_get(param->id, &param->config);
    }
    case WM_WINDOW_EVENT_MASK_SET:
    {
        GET_WM_REQUEST(param, WM_WINDOW_EVENT_MASK_SET, fr);
        wm_window_event_mask_set(param->id, param->event_mask);
        break;
    }
    case WM_WINDOW_SHOW:
    {
        GET_WM_REQUEST(param, WM_WINDOW_SHOW, fr);
        return wm_window_show(param->id);
    }
    case WM_WINDOW_HIDE:
    {
        GET_WM_REQUEST(param, WM_WINDOW_HIDE, fr);
        return wm_window_hide(param->id);
    }
    case WM_WINDOW_UPDATE:
    {
        GET_WM_REQUEST(param, WM_WINDOW_UPDATE, fr);
        wm_window_update(param->id);
        break;
    }
    case WM_WINDOW_RAISE:
    {
        GET_WM_REQUEST(param, WM_WINDOW_RAISE, fr);
        wm_window_raise(param->id);
        break;
    }
    case WM_WINDOW_MOVE:
    {
        GET_WM_REQUEST(param, WM_WINDOW_MOVE, fr);
        wm_window_move(param->id, param->dx, param->dy);
        break;
    }
    default:
        printf("Exception req:%d\n", fr->req);
        return -1;
    }
    return 0;
}
